# -*- coding: utf-8 -*-
import base64
import cv2
import datetime
import numpy as npy
from face_recognition.recognition.image_tools import ImageTools
from face_recognition.service.face_info_service import FaceInfoService
from face_recognition.service.user_info_service import UserInfoService
from face_recognition.utils.array_utils import ListTools
from face_recognition.utils.log_utils import LoggerUtil
from face_recognition.utils.pickle_utils import PickleUtil
from face_recognition.utils.string_utils import StringUtils


class FeatureManager:
	
	def __init__(self):
		self.train_faces = []
		self.train_face_labels = []
		self.recognizer = cv2.face.LBPHFaceRecognizer_create()
		self.image_tools = ImageTools()
		# 数据库查询服务对象
		self.user_info_service = UserInfoService()
		self.face_info_service = FaceInfoService()
	
	def load_features_and_labels(self):
		"""
		加载人脸特征集合以及特征标签集合
		:return: model(pickle)
		"""
		# 先尝试从本地文件中加载，如果没有再从数据库中加载
		# 从本地文件加载
		try:
			self.train_faces = PickleUtil.restore_features()
			self.train_face_labels = PickleUtil.restore_feature_labels()
		except Exception as err:
			LoggerUtil.error("系统从本地文件中获取特征以及标签信息时发生异常！")
		
		if ListTools.isEmpty(self.train_faces) or ListTools.isEmpty(self.train_face_labels):
			# 尝试从数据库加载特征
			LoggerUtil.info("系统尝试直接从数据库中获取特征集......")
			self.train_faces, self.train_face_labels = self.load_face_features_from_db([])
			if ListTools.isNotEmpty(self.train_faces) and ListTools.isNotEmpty(self.train_face_labels):
				LoggerUtil.info("系统尝试持久化人脸特征数据......")
				PickleUtil.dump_features(self.train_faces)
				LoggerUtil.info("系统尝试持久化人脸特征标签数据......")
				PickleUtil.dump_feature_labels(self.train_face_labels)
		return self.train_faces, self.train_face_labels
	
	def update_features_and_labels(self, features, label):
		"""
		将新的用户的人脸特征信息添加到训练数据中
		:param features:人脸特征集合
		:param label: 用户的数字编号
		:return:self.train_faces, self.train_face_labels
		"""
		if label is None or label <= 0:
			LoggerUtil.err("lable为空，不允许添加新的特征信息")
			return self.train_faces, self.train_face_labels
		
		if ListTools.isNotEmpty(features):
			# 先把该用户原来的特征删除掉
			label_ndarray = npy.array(self.train_face_labels)
			index_arr = npy.where( label_ndarray == label)
			# 如果index_arr不为空，则在label和features里删除相同下标的数据
			self.train_face_labels = npy.delete(label_ndarray, index_arr)
			self.train_faces = npy.delete(self.train_faces, index_arr)
			# 生成一个与特征集合相同长度的标签数组
			labels = [label for x in range(len(features))]
			# 再将需要添加的数据添加到数组里
			self.train_face_labels = self.train_face_labels.tolist() + labels
			self.train_faces = self.train_faces.tolist() + features
		return self.train_faces, self.train_face_labels
	
	def load_face_features_with_user_id(self, user_id):
		"""
		根据用户ID从数据库直接加载人脸特征信息和标签，并且返回
		:param user_id: 用户ID
		:return: train_faces(人脸特征数据集), train_face_labels（人脸标签数据集）
		"""
		# 开始从数据库中查询指定的用户的人脸特征
		if StringUtils.isNotEmpty(user_id):
			user = self.user_info_service.get_with_id(user_id)
			if user is not None:
				face_features = []
				LoggerUtil.debug("系统尝试获取用户：" + user.username + "的人像信息......")
				# 查询出该用户所有的人脸信息列表
				face_image_info_list = self.face_info_service.list_face_index_with_user_id(user_id)
				if ListTools.isNotEmpty(face_image_info_list):
					image_count = len(face_image_info_list)
					index = 0
					for image_info in face_image_info_list:
						index = index + 1
						LoggerUtil.debug("获取第" + str(index) + "/" + str(image_count) + "个人脸特征......")
						# 直接从数据库中获取特征值
						feature_content_string = self.face_info_service.get_feature_with_face_id(image_info.id)
						if StringUtils.isNotEmpty(feature_content_string):
							feature_content = ListTools.string_to_ndarray(feature_content_string)
						else:
							# 如果没有，再分析图片
							LoggerUtil.debug("特征信息不存在，开始分析第" + str(index) + "/" + str(image_count) + "个人脸图像中的人脸特征......")
							feature_content = self.analyse_feature_with_image(image_info.id)
							# 更新到数据库中，以便于下次直接获取
							feature_content_string = ListTools.ndarray_to_string(feature_content)
							self.face_info_service.update_feature_with_id(image_info.id, feature_content_string)
						if ListTools.isNotEmpty(feature_content):
							face_features.append(feature_content)
		return face_features
	
	def load_face_features_from_db(self, user_train_ids=[]):
		"""
		从数据库直接加载人脸特征信息和标签
		:param user_train_ids: 所有的用户的基础信息ID列表
		:return: train_faces(人脸特征数据集), train_face_labels（人脸标签数据集）
		"""
		start_time = datetime.datetime.now()
		self.train_faces = []
		self.train_face_labels = []
		# 如果传入的user_train_ids为空，那么查询所有有人脸图像的用户ID列表
		if ListTools.isEmpty(user_train_ids):
			user_train_ids = self.user_info_service.list_all_user_ids()
		# 开始从数据库中查询指定的用户的人脸特征
		if ListTools.isNotEmpty(user_train_ids):
			user_count = len(user_train_ids)
			user_index = 0
			for id in user_train_ids:
				user_index = user_index + 1
				user = self.user_info_service.get_with_id(id)
				# 如果user.face_count说明有人脸图片
				if user is not None and user.face_count > 0:
					LoggerUtil.debug(
						"系统尝试获取第" + str(user_index) + "/" + str(user_count) + "个用户：" + user.username + "的人像信息......")
					this_label = user.index_number
					# 查询出该用户所有的人脸信息列表
					face_image_info_list = self.face_info_service.list_face_index_with_user_id(id)
					if ListTools.isNotEmpty(face_image_info_list):
						image_count = len(face_image_info_list)
						index = 0
						for image_info in face_image_info_list:
							index = index + 1
							LoggerUtil.debug("获取第" + str(index) + "/" + str(image_count) + "个人脸特征......")
							feature_content_string = self.face_info_service.get_feature_with_face_id(image_info.id)
							if StringUtils.isNotEmpty(feature_content_string):
								feature_content = ListTools.string_to_ndarray(feature_content_string)
							else:
								LoggerUtil.debug("特征信息不存在，开始分析第" + str(index) + "/" + str(image_count) + "个人脸图像中的人脸特征......")
								feature_content = self.analyse_feature_with_image(image_info.id)
								# 更新到数据库中，以便于下次直接获取
								feature_content_string = ListTools.ndarray_to_string(feature_content)
								self.face_info_service.update_feature_with_id(image_info.id, feature_content_string)
							if ListTools.isNotEmpty(feature_content):
								self.train_faces.append(feature_content)
								self.train_face_labels.append(this_label)
							else:
								LoggerUtil.warn("用户" + user.username + "第" + str(index) + "/" + str(
									image_count) + "个人脸图像中未分析到人脸特征信息！")
		time_diff = datetime.datetime.now() - start_time
		LoggerUtil.info("已经成功获取并且分析所有的人脸特征及标签：" + str(time_diff) + "秒")
		return self.train_faces, self.train_face_labels
	
	def analyse_feature_with_image(self, face_id):
		"""
		从数据库加载并分析全量图片信息和标签
		:param face_id: 用户人脸信息
		:return: train_faces(人脸特征数据集), train_face_labels（人脸标签数据集）
		"""
		# 从数据库中查询该图像的base64信息，然后base64解码为图片数据
		base64_content = self.face_info_service.get_base64_with_face_id(face_id)
		# 检测人脸区域并且获取人脸特征
		return self.image_tools.get_feature_from_base64(base64_content)
	
	def analyse_feature_with_images(self, user_train_ids):
		"""
		从数据库加载并分析全量图片信息和标签
		:param user_train_ids: 所有的用户的基础信息ID列表
		:return: train_faces(人脸特征数据集), train_face_labels（人脸标签数据集）
		"""
		start_time = datetime.datetime.now()
		
		if ListTools.isEmpty(user_train_ids):
			user_train_ids = self.user_info_service.list_all_user_ids()
		
		if ListTools.isNotEmpty(user_train_ids):
			for id in user_train_ids:
				user = self.user_info_service.get_with_id(id)
				if user is not None:
					LoggerUtil.debug("尝试获取用户：" + user.username + "的人像信息......")
					this_label = user.index_number
					# 查询出该用户所有的人脸信息列表
					image_info_list = self.face_info_service.list_face_index_with_user_id(id)
					if ListTools.isNotEmpty(image_info_list):
						image_count = len(image_info_list)
						index = 0
						for image_info in image_info_list:
							index = index + 1
							LoggerUtil.debug("获取第" + str(index) + "/" + str(image_count) + "张图片......")
							feature_content = self.analyse_face_images(image_info.id)
							if ListTools.isNotEmpty(feature_content):
								self.train_faces.append(feature_content)
								self.train_face_labels.append(this_label)
		
		time_diff = datetime.datetime.now() - start_time
		LoggerUtil.debug("获取并且分析所有的训练人脸特征及标签：" + str(time_diff) + "秒")
		return self.train_faces, self.train_face_labels
